package org.xingole.jgit.utils.sha1;

import org.xingole.jgit.lib.ObjectId;
import org.xingole.jgit.utils.NetworkByteHandler;

import static java.lang.Integer.rotateLeft;

/**
 * <p>Pure Java implementation of SHA-1 from FIPS 180-1 / RFC 3174</p>
 *
 * <p>
 * See <a href="https://tools.ietf.org/html/rfc3174">RFC 3174</a>.
 *
 * <p> Unlike MessageDigest, this implementation includes the algorithm used by
 * {@code sha1dc} to detect cryptanalytic collision attacks against SHA-1, such
 * as the one used by <a href="https://shattered.it/">SHAttered</a>. See
 * <a href="https://github.com/cr-marcstevens/sha1collisiondetection">
 * sha1collisiondetection</a> for more information.</p>
 *
 * <p>A SHA-1 collision occurs when two different pieces of data hash to the same digest. This means that it is possible
 * to create two different sets of input data that produce the same hash value, which compromises the integrity
 * of the hash function.</p>
 *
 * <p>When detectCollision is true (default), this implementation throws Sha1CollisionException
 * from any digest method if a potential collision was detected.</p>
 *
 */
public class SHA1 {

    // The static method constructor pattern
    /**
     * @return : a new context to compute a SHA-1 hash of data
     */
    public static SHA1 newInstance() {
        return new SHA1();
    }

    // The second 5-word buffer
    private final State h = new State();

    /**
     * The words of the 80-word sequence.
     */
    private final int[] w = new int[80];

    // RFC 3174 Section 4: Message Padding
    /*
     * Implementation Notes:
     *
     * The data file or message(blob object) should be considered to be a bit string. The purpose of message
     * padding is to make the total length of a padded message a multiple of 512.
     *
     *  padded message:
     *                 -|----------------total length: n * 512 bits--------------|--
     *                 |     message content   | 1 | 0 ... 0 |   message length |
     *                ------------m_l bits------one----m bits-------64 bits---------
     *
     * A block is equal to 64 bytes.
     * The padded message will contain 16 * n words for some n > 0.
     *
     * The padded message is regarded as a sequence of n blocks M(1), M(2), ..., M(n).
     */

    /**
     * Total number of bytes in the message
     */
    private long length;

    /**
     * Buffer to accumulate partial blocks to 64 byte alignment.
     */
    private final byte[] buffer = new byte[64];

    // constructor for this class to avoid create sha1 instance directly
    private SHA1() {
        // first step to apply sha1 algorithm: initialize h
        h.init();
    }


    /**
     * Update the digest computation by adding bytes to the message.
     * @param in - input array of bytes which Java program will read bytes from
     * @param pos - position to start at from in.
     * @param len - number of byes to add to the message.
     */
    public void update(byte[] in, int pos, int len) {
        // SHA-1 compress can only process whole 64 byte blocks.
        // Hold partial updates in buffer, whose length is the low bits.

    }

    // RFC 3174 Section 6.1: Method 1

    private void compress(byte[] block, int pos) {
        // TODO: no implements the collision detection
        initBlock(block, pos);

        compress();
    }

    /**
     * Initialize the block.
     *
     * @param block - the buffer for the block to process
     * @param pos - the position of the block start in the buffer
     */
    private void initBlock(byte[] block, int pos) {
        /*
         * Implementation Notes:
         *
         * Initialize the block M(i):
         *
         * a. Divide M(i) into 16 words W(0), W(1), ... , W(15), where W(0) is the left-most word.
         *
         * b. For t = 16 to 79 let
         *    W(t) = S^1(W(t-3) XOR W(t-8) XOR W(t-14) XOR W(t-16)).
         */

        // RFC 3174 6.1.a, initialize the first 16 words
        for (int t = 0; t < 16; t++) {
            w[t] = NetworkByteHandler.decodeInt32(block, pos + (t << 2));
        }

        // RFC 3174 6.1.b, extends state vector to 80 words.
        for(int t = 16; t < 80; t++) {
            int x = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16];
            w[t] = rotateLeft(x, 1); // S^1(...)
        }
    }

    // compress the state vector
    private void compress() {
        // Method 1 from RFC 3174 section 6.1, but we use the loop statement to implement not the jgit implementation.
        int a = h.a, b = h.b, c = h.c, d = h.d, e = h.e;
        int temp = 0;

        /*
         * Implementation Notes:
         *
         * d. For t = 0 to 79 do
         *      TEMP = S^5(A) + f(t;B,C,D) + E + W(t) + K(t);
         *      E = D;  D = C;  C = S^30(B);  B = A; A = TEMP;
         *
         * e. Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E.
         *
         * After processing M(n), the message digest is the 160-bit string represented by the 5 words
         *              H0 H1 H2 H3 H4.
         */
        for(int t = 0; t < 80; t++) {
            temp = e;
            switch (t / 20) {
                case 0:
                    temp += s1(a, b, c, d, w[t]);
                    break;
                case 1:
                    temp += s2(a, b, c, d, w[t]);
                    break;
                case 2:
                    temp += s3(a, b, c, d, w[t]);
                    break;
                case 3:
                    temp += s4(a, b, c, d, w[t]);
                    break;
                default:  // em...maybe this case never happens !!
                    throw new ArrayIndexOutOfBoundsException();
            }
            e = d; d = c; c = rotateLeft(b, 30); b = a; a = temp;
        }

        h.save(h.a + a, h.b + b, h.c + c, h.d + d, h.e + e);
    }

    // RFC 3174 Section 5: Functions and Constants Used
    /*
     * Implementation Notes:
     *
     * A sequence of logical functions is used in SHA-1:
     *      f(t; B, C, D) = (B AND C) OR ((NOT B) AND D)           ( 0 <= t <= 19)
     *      f(t; B, C, D) = B XOR C XOR D                          (20 <= t <= 39)
     *      f(t; B, C, D) = (B AND C) OR (B AND D) OR (C AND D)    (40 <= t <= 59)
     *      f(t; B, C, D) = B XOR C XOR D                          (60 <= t <= 79)
     *
     * A sequence of constant words is used in the SHA-1:
     *      K(t) = 0x5A827999      ( 0 <= t <= 19)
     *      K(t) = 0x6ED9EBA1      (20 <= t <= 39)
     *      K(t) = 0x8F1BBCDC      (40 <= t <= 59)
     *      K(t) = 0xCA62C1D6      (60 <= t <= 79)
     *
     * The format to calculate the TEMP:
     *
     * Temp = S^5(A) + f(t;B, C, D) + E + W(t) + K(t)
     *
     * Therefore, we need to implement 5 types methods.
     */

    private static int s1(int a, int b, int c, int d, int w_t) {
        return rotateLeft(a, 5)
                // f: 0 <= t <= 19
                + ((b & c) | ((~b) & d))
                + 0x5A827999 + w_t;
    }

    private static int s2(int a, int b, int c, int d, int w_t) {
        return rotateLeft(a, 5)
                // f: 20 <= t <= 39
                + (b ^ c ^ d)
                + 0x6ED9EBA1 + w_t;
    }

    private static int s3(int a, int b, int c, int d, int w_t) {
        return rotateLeft(a, 5)
                // f: 40 <= t <= 59
                + ((b & c) | (b & d) | (c & d))
                + 0x8F1BBCDC + w_t;
    }

    private static int s4(int a, int b, int c, int d, int w_t) {
        return rotateLeft(a, 5)
                // f: 60 <= t <= 79
                + (b ^ c ^ d)
                + 0xCA62C1D6 + w_t;
    }

    /**
     * Reset this instance to compute another hash.
     *
     * <p>So we can reuse the instance so that we can only create only one instance to compute many sha1 hash.</p>
     *
     * @return : this
     */
    public SHA1 reset() {
        h.init();
        length = 0;
        return this;
    }

    // RFC 3174 Section 6: Computing the Message Digest
    /*
     * Implementation Notes:
     *
     *  Method 1:
     *
     * The words of the first 5-word buffer are labeled A, B, C, D, E.
     * The words of the second 5-word buffer are labeled H0, H1, H2, H3, H4.
     *
     * Oh! JGit model the State internal class as the 5-word buffer. Using SHA-1, the result has the same
     * characteristic.
     */
    private static final class State {
        int a;
        int b;
        int c;
        int d;
        int e;

        // For the second 5-word buffer
        final void init() {
            /*
             * Implementation Notes:
             *
             * Before processing any blocks, the H's are initialized as follows:
             *
             *   H0 = 67452301
             *
             *   H1 = EFCDAB89
             *
             *   H2 = 98BADCFE
             *
             *   H3 = 10325476
             *
             *   H4 = C3D2E1F0.
             */
            save(0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0);
        }

        // For the common buffer
        final void save(int a1, int b1, int c1, int d1, int e1) {
            a = a1;
            b = b1;
            c = c1;
            d = d1;
            e = e1;
        }

        // For the result of the SHA-1
        ObjectId toObjectId() {
            return new ObjectId(a, b, c, d, e);
        }
    }
}
